<script setup lang="ts">
import {nextTick, onMounted, reactive, ref, toRefs} from "vue";
import {WebSocketClient} from "@/api/websocket.ts";
import {renderMarkdown} from "@/commons/markdownRender.ts";

const data = reactive({
  welcome: '',
  messageList: new Array<{
    role: string,
    content: string
  }>(),
  question: '',
  tempAnswerStatus: 0,
  tempAnswerContent: null as string | null,
  loading: false,
  waitAnswer: false,
  wsClient: null as WebSocketClient | null
})

const {welcome, messageList, question, loading, waitAnswer} = toRefs(data)

const recordListRef = ref()

const clearMessageList = () => {
  data.wsClient?.close()
  data.messageList = []
  data.welcome = '您好！我是星火老年人陪伴助手，有什么可以帮您？'
}

const sendQuestion = (event?: KeyboardEvent) => {
  // 加载中、内容为空、输入法回车，不发送消息
  if (data.loading || data.question.trim() === '' || event?.isComposing) {
    return
  }

  // 开启等待
  data.loading = true

  // 启动WebSocket
  data.wsClient = new WebSocketClient(import.meta.env.VITE_API_URL + '/demo/spark/chat?token=' + new Date().getTime(), {
    onOpen: () => {
      // 存入历史记录
      data.messageList.push({
        role: 'user',
        content: data.question
      })
      // 连接成功后发送消息
      data.wsClient?.send(JSON.stringify({
        messageList: data.messageList,
      }));
      // 等待回复
      data.waitAnswer = true
      // 问题清空
      data.question = ''
      // 滚动条置底
      scrollToBottom()
    },
    onMessage: (message) => {
      // JSON格式消息
      const response = JSON.parse(message)

      // 错误信息
      if (response.code !== 200) {
        data.waitAnswer = false
        alert(response.message)
        return
      }

      // 开始回复，创建历史对话记录
      if (data.waitAnswer) {
        // 临时保存回答内容
        data.tempAnswerContent = response.data.content
        data.tempAnswerStatus = response.data.status

        // 第一次收到回答，创建一个消息记录
        data.waitAnswer = false
        data.messageList.push({
          role: 'assistant',
          content: ''
        })

        // 开始拼接回复内容
        startRenderAnswer()
      } else {
        // 临时保存回答内容
        data.tempAnswerContent += response.data.content
        data.tempAnswerStatus = response.data.status
      }
    },
    onClose: () => {
      data.loading = false
    },
    onError: () => {
      data.loading = false
    },
  });
}

// 拼接回复内容：实现逐字打印
const startRenderAnswer = async () => {
  while (data.tempAnswerContent != null) {
    if (data.tempAnswerContent) {
      const splitContent = data.tempAnswerContent.charAt(0)
      data.tempAnswerContent = data.tempAnswerContent.substring(1)
      data.messageList[data.messageList.length - 1].content += splitContent
      // 滚动条置底
      scrollToBottom()
    } else if (data.tempAnswerStatus === 2) {
      // 是否是结束回复

      // 清空临时保存的回答内容，设置为null以跳出循环
      data.tempAnswerContent = null
      data.tempAnswerStatus = 0

      // 关闭连接
      data.wsClient?.close()
    }
    // 等待30毫秒
    await simulateThreadWait(30)
  }
}

// 线程等待
const simulateThreadWait = (milliseconds: number) => {
  return new Promise(resolve => setTimeout(resolve, milliseconds));
}

const scrollToBottom = () => {
  nextTick(() => {
    recordListRef.value.scrollTop = recordListRef.value.scrollHeight
  })
}

onMounted(() => {
  clearMessageList()
})

</script>

<template>
  <div class="chat-page">
    <div class="record-list" ref="recordListRef">
      <div class="record record-ai">
        <div class="avatar">
          <img src="@/assets/images/ai.png" alt="ai">
        </div>
        <div class="content">{{ welcome }}</div>
      </div>
      <template v-for="(h, index) in messageList" :key="index">
        <div v-if="h.role === 'user'" class="record record-user">
          <div class="avatar">
            <img src="@/assets/images/user.png" alt="user">
          </div>
          <div class="content">{{ h.content }}</div>
        </div>
        <div v-else class="record record-ai">
          <div class="avatar">
            <img src="@/assets/images/ai.png" alt="ai">
          </div>
          <div class="content-markdown"
               v-html="renderMarkdown(h.content, index === messageList.length - 1 && loading)"></div>
        </div>
      </template>
      <div v-if="waitAnswer" class="record record-ai">
        <div class="avatar">
          <img src="@/assets/images/ai.png" alt="ai">
        </div>
        <div class="content">请稍等...</div>
      </div>
    </div>
    <div class="question-container">
      <textarea class="question-input" v-model="question"
                placeholder="在此输入你想要了解的内容，Shift+Enter换行"
                autofocus
                @keydown.enter.prevent="sendQuestion"
                maxlength="500"
                :readonly="loading"
      ></textarea>
      <div class="btn-group">
        <div class="send-btn" @click="sendQuestion()">发送</div>
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
.chat-page {
  width: 100%;
  max-width: 888px;
  height: 100vh;
  margin: 0 auto;
  box-sizing: border-box;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;

  .record-list {
    flex: 1;
    width: 100%;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 20px;

    .record {
      display: flex;
      align-items: flex-start;
      justify-content: flex-start;
      gap: 15px;

      .avatar {
        width: 28px;
        flex-shrink: 0;

        img {
          width: 100%;
        }
      }

      .content {
        white-space: pre-wrap;
        line-break: anywhere;
        background-color: #ffffff;
        padding: 10px;
        font-size: 15px;
        border-radius: 6px;
      }

      .content-markdown {
        line-break: anywhere;
        background-color: #ffffff;
        padding: 10px;
        font-size: 15px;
        border-radius: 6px;

        :deep(p) {
          margin-block: 0;
          margin: 0;
        }

        :deep(.record-loading) {
          color: #6087ff;
          font-weight: bolder;
          animation: alternateHideShow 900ms ease-in-out infinite;
        }

        @keyframes alternateHideShow {
          0%, 50%, 100% {
            opacity: 1;
          }
          60%, 90% {
            opacity: 0;
          }
        }
      }
    }
  }

  .question-container {
    flex-shrink: 0;
    width: 100%;
    height: 150px;
    background-color: #ffffff;
    box-sizing: border-box;
    border-radius: 6px;
    padding: 10px;
    display: flex;
    flex-direction: column;

    .question-input {
      flex: 1;
      width: 100%;
      resize: none;
      border: none;
      outline: none;
      font-size: 15px;
    }

    .btn-group {
      flex-shrink: 0;
      width: 100%;
      display: flex;
      flex-direction: row;
      justify-content: flex-end;

      .send-btn {
        font-size: 13px;
        background-color: #6279ff;
        color: #ffffff;
        padding: 10px 22px;
        border-radius: 6px;
        cursor: pointer;
        transition: all 200ms;

        &:hover {
          background-color: #6087ff;
        }
      }
    }
  }
}
</style>
